#include "client7z.h"
#include "../../../Common/MyWindows.h"
#include "../../../Common/MyInitGuid.h"
#include "../../../Common/Defs.h"
#include "../../../Common/IntToString.h"
#include "../../../Common/StringConvert.h"
#include "../../../Windows/DLL.h"
#include "../../../Windows/FileDir.h"
#include "../../../Windows/FileFind.h"
#include "../../../Windows/FileName.h"
#include "../../../Windows/NtCheck.h"
#include "../../../Windows/PropVariant.h"
#include "../../../Windows/PropVariantConv.h"
#include "../../Common/FileStreams.h"
#include "../../Archive/IArchive.h"
#include "../../IPassword.h"
#include "../../../../C/7zVersion.h"
#include <QMap>

HINSTANCE g_hInstance = NULL;
typedef QMap<int, GUID> MAP_GUID;
/// 能解压的格式:
DEFINE_GUID(CLSID_CFormat7z,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0x07, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatXz,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0x0C, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatZip,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0x01, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatTar,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xEE, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatGZip,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xEF, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatRar,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0x03, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatBZip2,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0x02, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatArj,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0x04, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatZ,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0x05, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatLzh,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0x06, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatCab,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0x08, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatNsis,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0x09, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatlzma,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0x0A, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatlzma86,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0x0B, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatppmd,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0x0D, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatCOFF,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xC6, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatExt,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xC7, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatVMDK,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xC8, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatVDI,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xC9, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatQcow,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xCA, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatGPT,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xCB, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatRar5,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xCC, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatIHex,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xCD, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatHxs,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xCE, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatTE,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xCF, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatUEFIc,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xD0, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatUEFIs,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xD1, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatSquashFS,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xD2, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatCramFS,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xD3, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatAPM,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xD4, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatMslz,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xD5, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatFlv,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xD6, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatSwf,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xD7, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatSwfc,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xD8, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatNtfs,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xD9, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatFat,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xDA, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatMbr,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xDB, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatVhd,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xDC, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatPes,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xDD, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatElf,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xDE, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatUdf,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xE0, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatXar,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xE1, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatMub,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xE2, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatHfs,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xE3, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatDmg,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xE4, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatCompound,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xE5, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatWim,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xE6, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatIso,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xE7, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatChm,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xE9, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatSplit,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xEA, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatRpm,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xEB, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatDeb,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xEC, 0x00, 0x00);

DEFINE_GUID(CLSID_CFormatCpio,
0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0xED, 0x00, 0x00);


using namespace NWindows;
using namespace NFile;
using namespace NDir;

#if 1
static const char * const kHelpString =
"Usage: 7zcl.exe [a | l | x] archive.7z [fileName ...]\n"
"Examples:\n"
"  7zcl.exe a archive.7z f1.txt f2.txt  : compress two files to archive.7z\n"
"  7zcl.exe l archive.7z   : List contents of archive.7z\n"
"  7zcl.exe x archive.7z   : eXtract files from archive.7z\n";


static void Convert_UString_to_AString(const UString &s, AString &temp)
{
  int codePage = CP_OEMCP;
  /*
  int g_CodePage = -1;
  int codePage = g_CodePage;
  if (codePage == -1)
    codePage = CP_OEMCP;
  if (codePage == CP_UTF8)
    ConvertUnicodeToUTF8(s, temp);
  else
  */
    UnicodeStringToMultiByte2(temp, s, (UINT)codePage);
}

static FString CmdStringToFString(const char *s)
{
  return us2fs(GetUnicodeString(s));
}

static void Print(const char *s)
{
    qDebug("%s", s);
}

static void Print(const AString &s)
{
  Print(s.Ptr());
}

static void Print(const UString &s)
{
  AString as;
  Convert_UString_to_AString(s, as);
  Print(as);
}

static void Print(const wchar_t *s)
{
  Print(UString(s));
}

static void PrintNewLine()
{
    //Print("\n");
    Print("");
}

static void PrintStringLn(const char *s)
{
  Print(s);
  PrintNewLine();
}

static void PrintError(const char *message)
{
  Print("Error: ");
  PrintNewLine();
  Print(message);
  PrintNewLine();
}

static void PrintError(const char *message, const FString &name)
{
  PrintError(message);
  Print(name);
}


static HRESULT IsArchiveItemProp(IInArchive *archive, UInt32 index, PROPID propID, bool &result)
{
  NCOM::CPropVariant prop;
  RINOK(archive->GetProperty(index, propID, &prop))
  if (prop.vt == VT_BOOL)
    result = VARIANT_BOOLToBool(prop.boolVal);
  else if (prop.vt == VT_EMPTY)
    result = false;
  else
    return E_FAIL;
  return S_OK;
}

static HRESULT IsArchiveItemFolder(IInArchive *archive, UInt32 index, bool &result)
{
  return IsArchiveItemProp(archive, index, kpidIsDir, result);
}


static const wchar_t * const kEmptyFileAlias = L"[Content]";


//////////////////////////////////////////////////////////////
// Archive Open callback class


class CArchiveOpenCallback Z7_final:
  public IArchiveOpenCallback,
  public ICryptoGetTextPassword,
  public CMyUnknownImp
{
  Z7_IFACES_IMP_UNK_2(IArchiveOpenCallback, ICryptoGetTextPassword)
public:

  bool PasswordIsDefined;
  UString Password;

  CArchiveOpenCallback() : PasswordIsDefined(false) {}
};

Z7_COM7F_IMF(CArchiveOpenCallback::SetTotal(const UInt64 * /* files */, const UInt64 * /* bytes */))
{
  return S_OK;
}

Z7_COM7F_IMF(CArchiveOpenCallback::SetCompleted(const UInt64 * /* files */, const UInt64 * /* bytes */))
{
  return S_OK;
}
  
Z7_COM7F_IMF(CArchiveOpenCallback::CryptoGetTextPassword(BSTR *password))
{
  if (!PasswordIsDefined)
  {
    // You can ask real password here from user
    // Password = GetPassword(OutStream);
    // PasswordIsDefined = true;
    PrintError("Password is not defined");
    return E_ABORT;
  }
  return StringToBstr(Password, password);
}



static const char * const kIncorrectCommand = "incorrect command";

//////////////////////////////////////////////////////////////
// Archive Extracting callback class

static const char * const kTestingString    =  "Testing     ";
static const char * const kExtractingString =  "Extracting  ";
static const char * const kSkippingString   =  "Skipping    ";
static const char * const kReadingString    =  "Reading     ";

static const char * const kUnsupportedMethod = "Unsupported Method";
static const char * const kCRCFailed = "CRC Failed";
static const char * const kDataError = "Data Error";
static const char * const kUnavailableData = "Unavailable data";
static const char * const kUnexpectedEnd = "Unexpected end of data";
static const char * const kDataAfterEnd = "There are some data after the end of the payload data";
static const char * const kIsNotArc = "Is not archive";
static const char * const kHeadersError = "Headers Error";


struct CArcTime
{
  FILETIME FT;
  UInt16 Prec;
  Byte Ns100;
  bool Def;

  CArcTime()
  {
    Clear();
  }

  void Clear()
  {
    FT.dwHighDateTime = FT.dwLowDateTime = 0;
    Prec = 0;
    Ns100 = 0;
    Def = false;
  }

  bool IsZero() const
  {
    return FT.dwLowDateTime == 0 && FT.dwHighDateTime == 0 && Ns100 == 0;
  }

  int GetNumDigits() const
  {
    if (Prec == k_PropVar_TimePrec_Unix ||
        Prec == k_PropVar_TimePrec_DOS)
      return 0;
    if (Prec == k_PropVar_TimePrec_HighPrec)
      return 9;
    if (Prec == k_PropVar_TimePrec_0)
      return 7;
    int digits = (int)Prec - (int)k_PropVar_TimePrec_Base;
    if (digits < 0)
      digits = 0;
    return digits;
  }

  void Write_To_FiTime(CFiTime &dest) const
  {
   #ifdef _WIN32
    dest = FT;
   #else
    if (FILETIME_To_timespec(FT, dest))
    if ((Prec == k_PropVar_TimePrec_Base + 8 ||
         Prec == k_PropVar_TimePrec_Base + 9)
        && Ns100 != 0)
    {
      dest.tv_nsec += Ns100;
    }
   #endif
  }

  void Set_From_Prop(const PROPVARIANT &prop)
  {
    FT = prop.filetime;
    unsigned prec = 0;
    unsigned ns100 = 0;
    const unsigned prec_Temp = prop.wReserved1;
    if (prec_Temp != 0
        && prec_Temp <= k_PropVar_TimePrec_1ns
        && prop.wReserved3 == 0)
    {
      const unsigned ns100_Temp = prop.wReserved2;
      if (ns100_Temp < 100)
      {
        ns100 = ns100_Temp;
        prec = prec_Temp;
      }
    }
    Prec = (UInt16)prec;
    Ns100 = (Byte)ns100;
    Def = true;
  }
};



class CArchiveExtractCallback Z7_final:
  public IArchiveExtractCallback,
  public ICryptoGetTextPassword,
  public CMyUnknownImp
{
  Z7_IFACES_IMP_UNK_2(IArchiveExtractCallback, ICryptoGetTextPassword)
  Z7_IFACE_COM7_IMP(IProgress)

  CMyComPtr<IInArchive> _archiveHandler;
  FString _directoryPath;  // Output directory
  UString _filePath;       // name inside arcvhive
  FString _diskFilePath;   // full path to file on disk
  bool _extractMode;
  struct CProcessedFileInfo
  {
    CArcTime MTime;
    UInt32 Attrib;
    bool isDir;
    bool Attrib_Defined;
  } _processedFileInfo;

  COutFileStream *_outFileStreamSpec;
  CMyComPtr<ISequentialOutStream> _outFileStream;

public:
  void Init(IInArchive *archiveHandler, const FString &directoryPath);

  UInt64 NumErrors;
  bool PasswordIsDefined;
  UString Password;
  UInt64 FileSize;

  CArchiveExtractCallback() : PasswordIsDefined(false) {}
};

void CArchiveExtractCallback::Init(IInArchive *archiveHandler, const FString &directoryPath)
{
  NumErrors = 0;
  _archiveHandler = archiveHandler;
  _directoryPath = directoryPath;
  NName::NormalizeDirPathPrefix(_directoryPath);
}

Z7_COM7F_IMF(CArchiveExtractCallback::SetTotal(UInt64 size))
{
    FileSize = size;

    ServerZip::getInstance()->ServerZip::getInstance()->m_callbackServerZip->ICallExtractFileSize(FileSize);

    return S_OK;
}

Z7_COM7F_IMF(CArchiveExtractCallback::SetCompleted(const UInt64 * completeValue))
{
    ServerZip::getInstance()->m_callbackServerZip->ICallExtractComplete(static_cast<float>(*completeValue) / FileSize * 100.0f);
    return S_OK;
}

Z7_COM7F_IMF(CArchiveExtractCallback::GetStream(UInt32 index,
    ISequentialOutStream **outStream, Int32 askExtractMode))
{
  *outStream = NULL;
  _outFileStream.Release();

  {
    // Get Name
    NCOM::CPropVariant prop;
    RINOK(_archiveHandler->GetProperty(index, kpidPath, &prop))
    
    UString fullPath;
    if (prop.vt == VT_EMPTY)
      fullPath = kEmptyFileAlias;
    else
    {
      if (prop.vt != VT_BSTR)
        return E_FAIL;
      fullPath = prop.bstrVal;
    }
    _filePath = fullPath;
  }

  if (askExtractMode != NArchive::NExtract::NAskMode::kExtract)
    return S_OK;

  {
    // Get Attrib
    NCOM::CPropVariant prop;
    RINOK(_archiveHandler->GetProperty(index, kpidAttrib, &prop))
    if (prop.vt == VT_EMPTY)
    {
      _processedFileInfo.Attrib = 0;
      _processedFileInfo.Attrib_Defined = false;
    }
    else
    {
      if (prop.vt != VT_UI4)
        return E_FAIL;
      _processedFileInfo.Attrib = prop.ulVal;
      _processedFileInfo.Attrib_Defined = true;
    }
  }

  RINOK(IsArchiveItemFolder(_archiveHandler, index, _processedFileInfo.isDir))

  {
    _processedFileInfo.MTime.Clear();
    // Get Modified Time
    NCOM::CPropVariant prop;
    RINOK(_archiveHandler->GetProperty(index, kpidMTime, &prop))
    switch (prop.vt)
    {
      case VT_EMPTY:
        // _processedFileInfo.MTime = _utcMTimeDefault;
        break;
      case VT_FILETIME:
        _processedFileInfo.MTime.Set_From_Prop(prop);
        break;
      default:
        return E_FAIL;
    }

  }
  {
    // Get Size
    NCOM::CPropVariant prop;
    RINOK(_archiveHandler->GetProperty(index, kpidSize, &prop))
    UInt64 newFileSize;
    /* bool newFileSizeDefined = */ ConvertPropVariantToUInt64(prop, newFileSize);
  }

  
  {
    // Create folders for file
    int slashPos = _filePath.ReverseFind_PathSepar();
    if (slashPos >= 0)
      CreateComplexDir(_directoryPath + us2fs(_filePath.Left(slashPos)));
  }

  FString fullProcessedPath = _directoryPath + us2fs(_filePath);
  _diskFilePath = fullProcessedPath;

  if (_processedFileInfo.isDir)
  {
    CreateComplexDir(fullProcessedPath);
  }
  else
  {
    NFind::CFileInfo fi;
    if (fi.Find(fullProcessedPath))
    {
      if (!DeleteFileAlways(fullProcessedPath))
      {
        PrintError("Cannot delete output file", fullProcessedPath);
        return E_ABORT;
      }
    }
    
    _outFileStreamSpec = new COutFileStream;
    CMyComPtr<ISequentialOutStream> outStreamLoc(_outFileStreamSpec);
    if (!_outFileStreamSpec->Open(fullProcessedPath, CREATE_ALWAYS))
    {
      PrintError("Cannot open output file", fullProcessedPath);
      return E_ABORT;
    }
    _outFileStream = outStreamLoc;
    *outStream = outStreamLoc.Detach();
  }
  return S_OK;
}

Z7_COM7F_IMF(CArchiveExtractCallback::PrepareOperation(Int32 askExtractMode))
{
  _extractMode = false;
  switch (askExtractMode)
  {
    case NArchive::NExtract::NAskMode::kExtract:  _extractMode = true; break;
  }
    QString filepath = QString::fromWCharArray(_filePath.Ptr());
    filepath.replace(QString("\\"), QChar('/'));
    switch (askExtractMode)
    {
    case NArchive::NExtract::NAskMode::kExtract:
        //Print(kExtractingString);
        //qDebug() << kExtractingString << filepath;
        ServerZip::getInstance()->m_callbackServerZip->ICallExtractFileName(filepath);
        break;
    case NArchive::NExtract::NAskMode::kTest:
        //Print(kTestingString);
        //qDebug() << kTestingString << filepath;
        break;
    case NArchive::NExtract::NAskMode::kSkip:
        //Print(kSkippingString);
        //qDebug() << kSkippingString << filepath;
        break;
    }
    //Print(_filePath);
    return S_OK;
}

Z7_COM7F_IMF(CArchiveExtractCallback::SetOperationResult(Int32 operationResult))
{
  switch (operationResult)
  {
    case NArchive::NExtract::NOperationResult::kOK:
      break;
    default:
    {
      NumErrors++;
      Print("  :  ");
      const char *s = NULL;
      switch (operationResult)
      {
        case NArchive::NExtract::NOperationResult::kUnsupportedMethod:
          s = kUnsupportedMethod;
            ServerZip::getInstance()->SetExtractError(EXTRACT_RESULT_UNSUPPORTEDMETHOD);
          break;
        case NArchive::NExtract::NOperationResult::kCRCError:
          s = kCRCFailed;
            ServerZip::getInstance()->SetExtractError(EXTRACT_RESULT_CRCERROR);
          break;
        case NArchive::NExtract::NOperationResult::kDataError:
          s = kDataError;
            ServerZip::getInstance()->SetExtractError(EXTRACT_RESULT_DATAERROR);
          break;
        case NArchive::NExtract::NOperationResult::kUnavailable:
          s = kUnavailableData;
            ServerZip::getInstance()->SetExtractError(EXTRACT_RESULT_UNAVAILABLEERROR);
          break;
        case NArchive::NExtract::NOperationResult::kUnexpectedEnd:
          s = kUnexpectedEnd;
            ServerZip::getInstance()->SetExtractError(EXTRACT_RESULT_UNEXPECTEDEND);
          break;
        case NArchive::NExtract::NOperationResult::kDataAfterEnd:
          s = kDataAfterEnd;
            ServerZip::getInstance()->SetExtractError(EXTRACT_RESULT_DATAAFTEREND);
          break;
        case NArchive::NExtract::NOperationResult::kIsNotArc:
          s = kIsNotArc;
            ServerZip::getInstance()->SetExtractError(EXTRACT_RESULT_ISNOTARC);
          break;
        case NArchive::NExtract::NOperationResult::kHeadersError:
          s = kHeadersError;
            ServerZip::getInstance()->SetExtractError(EXTRACT_RESULT_HEADERSERROR);
          break;
      }
      if (s)
      {
        Print("Error : ");
        Print(s);
      }
      else
      {
        char temp[16];
        ConvertUInt32ToString((UInt32)operationResult, temp);
        Print("Error #");
        Print(temp);
      }
    }
  }

  if (_outFileStream)
  {
    if (_processedFileInfo.MTime.Def)
    {
      CFiTime ft;
      _processedFileInfo.MTime.Write_To_FiTime(ft);
      _outFileStreamSpec->SetMTime(&ft);
    }
    RINOK(_outFileStreamSpec->Close())
  }
  _outFileStream.Release();
  if (_extractMode && _processedFileInfo.Attrib_Defined)
    SetFileAttrib_PosixHighDetect(_diskFilePath, _processedFileInfo.Attrib);
  PrintNewLine();
  return S_OK;
}


Z7_COM7F_IMF(CArchiveExtractCallback::CryptoGetTextPassword(BSTR *password))
{
  if (!PasswordIsDefined)
  {
    // You can ask real password here from user
    // Password = GetPassword(OutStream);
    // PasswordIsDefined = true;
    PrintError("Password is not defined");
    return E_ABORT;
  }
  return StringToBstr(Password, password);
}



//////////////////////////////////////////////////////////////
// Archive Creating callback class

struct CDirItem: public NWindows::NFile::NFind::CFileInfoBase
{
  UString Path_For_Handler;
  FString FullPath; // for filesystem

  CDirItem(const NWindows::NFile::NFind::CFileInfo &fi):
      CFileInfoBase(fi)
    {}
};

class CArchiveUpdateCallback Z7_final:
  public IArchiveUpdateCallback2,
  public ICryptoGetTextPassword2,
  public CMyUnknownImp
{
  Z7_IFACES_IMP_UNK_2(IArchiveUpdateCallback2, ICryptoGetTextPassword2)
  Z7_IFACE_COM7_IMP(IProgress)
  Z7_IFACE_COM7_IMP(IArchiveUpdateCallback)

public:
  CRecordVector<UInt64> VolumesSizes;
  UString VolName;
  UString VolExt;

  FString DirPrefix;
  const CObjectVector<CDirItem> *DirItems;

  bool PasswordIsDefined;
  UString Password;
  bool AskPassword;
  bool m_NeedBeClosed;
  UInt64 FileSize;

  FStringVector FailedFiles;
  CRecordVector<HRESULT> FailedCodes;

  CArchiveUpdateCallback():
      DirItems(NULL),
      PasswordIsDefined(false),
      AskPassword(false)
      {}

  ~CArchiveUpdateCallback() { Finilize(); }
  HRESULT Finilize();

  void Init(const CObjectVector<CDirItem> *dirItems)
  {
    DirItems = dirItems;
    m_NeedBeClosed = false;
    FailedFiles.Clear();
    FailedCodes.Clear();
  }
};

Z7_COM7F_IMF(CArchiveUpdateCallback::SetTotal(UInt64 size))
{
    FileSize = size;
    ServerZip::getInstance()->m_callbackServerZip->ICallCompressFileSize(FileSize);
    return S_OK;
}

Z7_COM7F_IMF(CArchiveUpdateCallback::SetCompleted(const UInt64 * completeValue ))
{
    ServerZip::getInstance()->m_callbackServerZip->ICallCompressComplete(static_cast<float>(*completeValue) / FileSize * 100.0f);
    return S_OK;
}

Z7_COM7F_IMF(CArchiveUpdateCallback::GetUpdateItemInfo(UInt32 /* index */,
      Int32 *newData, Int32 *newProperties, UInt32 *indexInArchive))
{
  if (newData)
    *newData = BoolToInt(true);
  if (newProperties)
    *newProperties = BoolToInt(true);
  if (indexInArchive)
    *indexInArchive = (UInt32)(Int32)-1;
  return S_OK;
}

Z7_COM7F_IMF(CArchiveUpdateCallback::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value))
{
  NCOM::CPropVariant prop;
  
  if (propID == kpidIsAnti)
  {
    prop = false;
    prop.Detach(value);
    return S_OK;
  }

  {
    const CDirItem &di = (*DirItems)[index];
    switch (propID)
    {
      case kpidPath:  prop = di.Path_For_Handler; break;
      case kpidIsDir:  prop = di.IsDir(); break;
      case kpidSize:  prop = di.Size; break;
      case kpidCTime:  PropVariant_SetFrom_FiTime(prop, di.CTime); break;
      case kpidATime:  PropVariant_SetFrom_FiTime(prop, di.ATime); break;
      case kpidMTime:  PropVariant_SetFrom_FiTime(prop, di.MTime); break;
      case kpidAttrib:  prop = (UInt32)di.GetWinAttrib(); break;
      case kpidPosixAttrib: prop = (UInt32)di.GetPosixAttrib(); break;
    }
  }
  prop.Detach(value);
  return S_OK;
}

HRESULT CArchiveUpdateCallback::Finilize()
{
  if (m_NeedBeClosed)
  {
    PrintNewLine();
    m_NeedBeClosed = false;
  }
  return S_OK;
}

static void GetStream2(const wchar_t *name)
{
  Print("Compressing  ");
  if (name[0] == 0)
    name = kEmptyFileAlias;
  Print(name);
}

Z7_COM7F_IMF(CArchiveUpdateCallback::GetStream(UInt32 index, ISequentialInStream **inStream))
{
  RINOK(Finilize())

  const CDirItem &dirItem = (*DirItems)[index];
  GetStream2(dirItem.Path_For_Handler);
 
  if (dirItem.IsDir())
    return S_OK;

  {
    CInFileStream *inStreamSpec = new CInFileStream;
    CMyComPtr<ISequentialInStream> inStreamLoc(inStreamSpec);
    FString path = DirPrefix + dirItem.FullPath;
    if (!inStreamSpec->Open(path))
    {
      const DWORD sysError = ::GetLastError();
      FailedCodes.Add(HRESULT_FROM_WIN32(sysError));
      FailedFiles.Add(path);
      // if (systemError == ERROR_SHARING_VIOLATION)
      {
        PrintNewLine();
        PrintError("WARNING: can't open file");
        // Print(NError::MyFormatMessageW(systemError));
        return S_FALSE;
      }
      // return sysError;
    }
    *inStream = inStreamLoc.Detach();
  }
  return S_OK;
}

Z7_COM7F_IMF(CArchiveUpdateCallback::SetOperationResult(Int32 /* operationResult */))
{
  m_NeedBeClosed = true;
  return S_OK;
}

Z7_COM7F_IMF(CArchiveUpdateCallback::GetVolumeSize(UInt32 index, UInt64 *size))
{
  if (VolumesSizes.Size() == 0)
    return S_FALSE;
  if (index >= (UInt32)VolumesSizes.Size())
    index = VolumesSizes.Size() - 1;
  *size = VolumesSizes[index];
  return S_OK;
}

Z7_COM7F_IMF(CArchiveUpdateCallback::GetVolumeStream(UInt32 index, ISequentialOutStream **volumeStream))
{
  wchar_t temp[16];
  ConvertUInt32ToString(index + 1, temp);
  UString res = temp;
  while (res.Len() < 2)
    res.InsertAtFront(L'0');
  UString fileName = VolName;
  fileName.Add_Dot();
  fileName += res;
  fileName += VolExt;
  COutFileStream *streamSpec = new COutFileStream;
  CMyComPtr<ISequentialOutStream> streamLoc(streamSpec);
  if (!streamSpec->Create(us2fs(fileName), false))
    return GetLastError_noZero_HRESULT();
  *volumeStream = streamLoc.Detach();
  return S_OK;
}

Z7_COM7F_IMF(CArchiveUpdateCallback::CryptoGetTextPassword2(Int32 *passwordIsDefined, BSTR *password))
{
  if (!PasswordIsDefined)
  {
    if (AskPassword)
    {
      // You can ask real password here from user
      // Password = GetPassword(OutStream);
      // PasswordIsDefined = true;
      PrintError("Password is not defined");
      return E_ABORT;
    }
  }
  *passwordIsDefined = BoolToInt(PasswordIsDefined);
  return StringToBstr(Password, password);
}
#endif


///////////////////////////////////


ServerZip * ServerZip::m_q7zip = NULL;
const QString kDllName = "7z.dll";
ServerZip::ServerZip() :m_7zLib(kDllName)
{
    m_callbackServerZip = NULL;
    m_q7zip = this;
    m_nExtractError = EXTRACT_RESULT_NO_ERROR;
}

ServerZip::~ServerZip()
{
}

ServerZip *ServerZip::getInstance(void)
{
    if (m_q7zip == NULL)
    {
        m_q7zip = new ServerZip;
    }
    return m_q7zip;
}

int ServerZip::Init(void)
{
    int init_result = 1;
    bool loadResult = m_7zLib.load();

    if (true == loadResult){
        init_result = 0;
    }
    else{
        qDebug() << "Q7Zip" << kDllName << "load failure!!!";
        init_result = 1;
    }

    return init_result;
}

const GUID GetFileformat(QString archive_name)
{
    QString strFormat = ("");
    strFormat = archive_name.right(archive_name.size() - archive_name.lastIndexOf('.') - 1);
    if (strFormat.compare(QString("Zip"), Qt::CaseInsensitive) == 0)
    {
        return CLSID_CFormatZip;
    }
    else if (strFormat.compare(QString("Rar"), Qt::CaseInsensitive) == 0)
    {
        return CLSID_CFormatRar;
    }
    else if (strFormat.compare(QString("Tar"), Qt::CaseInsensitive) == 0)
    {
        return CLSID_CFormatTar;
    }
    else if (strFormat.compare(QString("GZip"), Qt::CaseInsensitive) == 0)
    {
        return CLSID_CFormatGZip;
    }
    else if (strFormat.compare(QString("xz"), Qt::CaseInsensitive) == 0)
    {
        return CLSID_CFormatXz;
    }
    else if (strFormat.compare(QString("7z"), Qt::CaseInsensitive) == 0)
    {
        return CLSID_CFormat7z;
    }
    else if (strFormat.compare(QString("Iso"), Qt::CaseInsensitive) == 0)
    {
        return CLSID_CFormatIso;
    }
    /// 默认7zip解吧
    else
    {
        return CLSID_CFormat7z;
    }
}

QString ServerZip::Lzma_sdk_version(void)
{
    return QString(MY_VERSION " ("  MY_DATE  ")");
}

int ServerZip::Compress(const QString &archive_name, const QStringList &compress_filelist, const QString &working_path, \
    bool PasswordIsDefined, QString password)
{
    Func_CreateObject createObjectFunc = (Func_CreateObject)m_7zLib.resolve("CreateObject");
    if (!createObjectFunc)
    {
        return 1;
    }

    CObjectVector<CDirItem> dirItems;
    {
        for (const QString &name : compress_filelist)
        {
            QString compress_name;
            NFind::CFileInfo fi;
            CDirItem di(fi);
            const wchar_t * pName = reinterpret_cast<const wchar_t *>(name.utf16());
            if (!fi.Find(pName))
            {
                return 1;
            }

            if (true == name.startsWith(working_path)){
                QString temp_filename = name;
                compress_name = temp_filename.remove(working_path);
            }

            const wchar_t * pCompressName = reinterpret_cast<const wchar_t *>(compress_name.utf16());

            di.Path_For_Handler = fs2us(pCompressName);
            di.FullPath = pName;
            dirItems.Add(di);
        }
    }

    COutFileStream *outFileStreamSpec = new COutFileStream;
    CMyComPtr<IOutStream> outFileStream = outFileStreamSpec;
    const wchar_t * parchive_name = reinterpret_cast<const wchar_t *>(archive_name.utf16());
    if (!outFileStreamSpec->Create(parchive_name, true))
    {
        PrintError("can't create archive file");
        return 1;
    }

    CMyComPtr<IOutArchive> outArchive;
    std::unique_ptr<GUID> fileFromat = std::unique_ptr<GUID>(new GUID(GetFileformat(archive_name)));
    if (createObjectFunc(fileFromat.get(), &IID_IOutArchive, (void **)&outArchive) != S_OK)
    {
        return 1;
    }

    CArchiveUpdateCallback *updateCallbackSpec = new CArchiveUpdateCallback;
    CMyComPtr<IArchiveUpdateCallback2> updateCallback(updateCallbackSpec);
    updateCallbackSpec->Init(&dirItems);
    updateCallbackSpec->PasswordIsDefined = PasswordIsDefined;
    updateCallbackSpec->Password = UString(password.toStdString().c_str());

    HRESULT result = outArchive->UpdateItems(outFileStream, dirItems.Size(), updateCallback);

    updateCallbackSpec->Finilize();

    if (result != S_OK)
    {
        PrintError("Update Error");
        return 1;
    }

    FOR_VECTOR(i, updateCallbackSpec->FailedFiles)
    {
        PrintNewLine();
        PrintError("Error for file", updateCallbackSpec->FailedFiles[i]);
    }

    if (updateCallbackSpec->FailedFiles.Size() != 0){
        return 1;
    }

    return 0;
}
void InitGuidMap(MAP_GUID& mapGuid)
{
    mapGuid.insert(0, CLSID_CFormat7z);
    mapGuid.insert(1, CLSID_CFormatZip);
    mapGuid.insert(2, CLSID_CFormatRar);
    mapGuid.insert(3, CLSID_CFormatTar);
    mapGuid.insert(4, CLSID_CFormatRar5);
    mapGuid.insert(5, CLSID_CFormatXz);
    mapGuid.insert(6, CLSID_CFormatBZip2);
    mapGuid.insert(7, CLSID_CFormatGZip);
    mapGuid.insert(8, CLSID_CFormatZ);
    mapGuid.insert(9, CLSID_CFormatExt);
    mapGuid.insert(10, CLSID_CFormatCab);
    mapGuid.insert(11, CLSID_CFormatFat);
    mapGuid.insert(12, CLSID_CFormatlzma);
    mapGuid.insert(13, CLSID_CFormatlzma86);
    mapGuid.insert(14, CLSID_CFormatXar);
    mapGuid.insert(15, CLSID_CFormatIso);
    mapGuid.insert(16, CLSID_CFormatNtfs);
    mapGuid.insert(17, CLSID_CFormatFlv);
    mapGuid.insert(18, CLSID_CFormatVDI);
    mapGuid.insert(19, CLSID_CFormatQcow);
    mapGuid.insert(20, CLSID_CFormatGPT);
    mapGuid.insert(21, CLSID_CFormatArj);
    mapGuid.insert(22, CLSID_CFormatIHex);
    mapGuid.insert(23, CLSID_CFormatHxs);
    mapGuid.insert(24, CLSID_CFormatTE);
    mapGuid.insert(25, CLSID_CFormatUEFIc);
    mapGuid.insert(26, CLSID_CFormatUEFIs);
    mapGuid.insert(27, CLSID_CFormatSquashFS);
    mapGuid.insert(28, CLSID_CFormatCramFS);
    mapGuid.insert(29, CLSID_CFormatAPM);
    mapGuid.insert(30, CLSID_CFormatMslz);
    mapGuid.insert(31, CLSID_CFormatVMDK);
    mapGuid.insert(32, CLSID_CFormatSwf);
    mapGuid.insert(33, CLSID_CFormatSwfc);
    mapGuid.insert(34, CLSID_CFormatLzh);
    mapGuid.insert(35, CLSID_CFormatNsis);
    mapGuid.insert(36, CLSID_CFormatMbr);
    mapGuid.insert(37, CLSID_CFormatVhd);
    mapGuid.insert(38, CLSID_CFormatPes);
    mapGuid.insert(39, CLSID_CFormatElf);
    mapGuid.insert(40, CLSID_CFormatUdf);
    mapGuid.insert(41, CLSID_CFormatppmd);
    mapGuid.insert(42, CLSID_CFormatHfs);
    mapGuid.insert(43, CLSID_CFormatDmg);
    mapGuid.insert(44, CLSID_CFormatCompound);
    mapGuid.insert(45, CLSID_CFormatCOFF);
    mapGuid.insert(46, CLSID_CFormatChm);
    mapGuid.insert(47, CLSID_CFormatSplit);
    mapGuid.insert(48, CLSID_CFormatRpm);
    mapGuid.insert(49, CLSID_CFormatDeb);
    mapGuid.insert(50, CLSID_CFormatCpio);
}

int ServerZip::Extract(const QString &archive_name, const QString &output_path)
{
    MAP_GUID mapGuid;
    mapGuid.clear();
    InitGuidMap(mapGuid);

    Func_CreateObject createObjectFunc = (Func_CreateObject)m_7zLib.resolve("CreateObject");
    if (!createObjectFunc)
    {
        PrintError("Can not get CreateObject");
        return 1;
    }

    CMyComPtr<IInArchive> archive;	/// 解压功能对象
    CInFileStream *fileSpec = new CInFileStream;
    CMyComPtr<IInStream> file = fileSpec;
    const wchar_t * parchive_name = reinterpret_cast<const wchar_t *>(archive_name.utf16());
    CArchiveOpenCallback *openCallbackSpec = new CArchiveOpenCallback;
    CMyComPtr<IArchiveOpenCallback> openCallback(openCallbackSpec);
    const UInt64 scanSize = 1 << 23;
    openCallbackSpec->PasswordIsDefined = false;
    LONG openResult = -1;


    for (MAP_GUID::ConstIterator iter = mapGuid.constBegin(); iter != mapGuid.constEnd(); iter++)
    {
        openCallbackSpec->PasswordIsDefined = false;
        LONG openResult = -1;
        bool bInitSuccess = false;

        if (createObjectFunc(&(iter.value()), &IID_IInArchive, (void **)&archive) != S_OK)
        {
            qDebug() << ("%d Can not get class object !", iter.key());
            continue;
        }

        if (!fileSpec->Open(parchive_name))
        {
            qDebug() << ("%d Can not open archive file", iter.key());
            continue;
        }

        openResult = archive->Open(file, &scanSize, openCallback);
        if (openResult != S_OK)
        {
            qDebug() << ("%d Can not open file as archive", iter.key());
            continue;
        }
        else
        {
            qDebug() << ("%d open file as archive", iter.key());
            bInitSuccess = true;
            break;
        }
    }


    // Extract command
    CArchiveExtractCallback *extractCallbackSpec = new CArchiveExtractCallback;
    CMyComPtr<IArchiveExtractCallback> extractCallback(extractCallbackSpec);
    const wchar_t * poutput_path = reinterpret_cast<const wchar_t *>(output_path.utf16());
    extractCallbackSpec->Init(archive, FString(poutput_path)); // second parameter is output folder path
    extractCallbackSpec->PasswordIsDefined = false;

    HRESULT result = archive->Extract(NULL, (UInt32)(Int32)(-1), false, extractCallback);

    int nExtractError = GetExtractError();
    if (nExtractError == EXTRACT_RESULT_NO_ERROR)
    {
        if (result != S_OK)
        {
            PrintError("Extract Error");
            return EXTRACT_RESULT_EXTRACT_RESULT_FAILED;
        }
        else
        {
            return EXTRACT_RESULT_NO_ERROR;
        }
    }
    else
    {
        return nExtractError;
    }
}

int ServerZip::Showfilelist(const QString &archive_name)
{
    Q_UNUSED(archive_name);
    return 0;
}

void ServerZip::RegisterCallbackServerZip(ICallbackServerZip* callServerZip)
{
    m_callbackServerZip = callServerZip;
}

void ServerZip::SetExtractError(emExtractError emError)
{
    m_nExtractError = emError;
}

const int ServerZip::GetExtractError()
{
    return m_nExtractError;
}






